/**
 * Copyright (c) 2008-2012, JGraph Ltd
 */
package com.mxgraph.swing.handler;

import com.mxgraph.swing.mxGraphComponent;
import com.mxgraph.swing.util.mxSwingConstants;
import com.mxgraph.util.mxRectangle;
import com.mxgraph.view.mxCellState;
import com.mxgraph.view.mxGraph;

import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseEvent;

/**
 * The type Mx cell handler.
 *
 * @author Administrator
 */
public class mxCellHandler {
    /**
     * Reference to the enclosing graph component.
     */
    protected mxGraphComponent graphComponent;

    /**
     * Holds the cell state associated with this handler.
     */
    protected mxCellState state;

    /**
     * Holds the rectangles that define the handles.
     */
    protected Rectangle[] handles;

    /**
     * Specifies if the handles should be painted. Default is true.
     */
    protected boolean handlesVisible = true;

    /**
     * Holds the bounding box of the handler.
     */
    protected transient Rectangle bounds;

    /**
     * Holds the component that is used for preview.
     */
    protected transient JComponent preview;

    /**
     * Holds the start location of the mouse gesture.
     */
    protected transient Point first;

    /**
     * Holds the index of the handle that was clicked.
     */
    protected transient int index;

    /**
     * Constructs a new cell handler for the given cell state.
     *
     * @param graphComponent Enclosing graph component.
     * @param state          Cell state for which the handler is created.
     */
    public mxCellHandler(mxGraphComponent graphComponent, mxCellState state) {
        this.graphComponent = graphComponent;
        refresh(state);
    }

    /**
     * Is active boolean.
     *
     * @return the boolean
     */
    public boolean isActive() {
        return first != null;
    }

    /**
     * Refreshes the cell handler.
     *
     * @param state the state
     */
    public void refresh(mxCellState state) {
        this.state = state;
        handles = createHandles();
        mxGraph graph = graphComponent.getGraph();
        mxRectangle tmp = graph.getBoundingBox(state.getCell());

        if (tmp != null) {
            bounds = tmp.getRectangle();

            if (handles != null) {
                for (int i = 0; i < handles.length; i++) {
                    if (isHandleVisible(i)) {
                        bounds.add(handles[i]);
                    }
                }
            }
        }
    }

    /**
     * Gets graph component.
     *
     * @return the graph component
     */
    public mxGraphComponent getGraphComponent() {
        return graphComponent;
    }

    /**
     * Returns the cell state that is associated with this handler.
     *
     * @return the state
     */
    public mxCellState getState() {
        return state;
    }

    /**
     * Returns the index of the current handle.
     *
     * @return the index
     */
    public int getIndex() {
        return index;
    }

    /**
     * Returns the bounding box of this handler.
     *
     * @return the bounds
     */
    public Rectangle getBounds() {
        return bounds;
    }

    /**
     * Returns true if the label is movable.
     *
     * @return the boolean
     */
    public boolean isLabelMovable() {
        mxGraph graph = graphComponent.getGraph();
        String label = graph.getLabel(state.getCell());

        return graph.isLabelMovable(state.getCell()) && label != null
                && label.length() > 0;
    }

    /**
     * Returns true if the handles should be painted.
     *
     * @return the boolean
     */
    public boolean isHandlesVisible() {
        return handlesVisible;
    }

    /**
     * Specifies if the handles should be painted.
     *
     * @param handlesVisible the handles visible
     */
    public void setHandlesVisible(boolean handlesVisible) {
        this.handlesVisible = handlesVisible;
    }

    /**
     * Returns true if the given index is the index of the last handle.
     *
     * @param index the index
     * @return the boolean
     */
    public boolean isLabel(int index) {
        return index == getHandleCount() - 1;
    }

    /**
     * Creates the rectangles that define the handles.
     *
     * @return the rectangle [ ]
     */
    protected Rectangle[] createHandles() {
        return null;
    }

    /**
     * Returns the number of handles in this handler.
     *
     * @return the handle count
     */
    protected int getHandleCount() {
        return (handles != null) ? handles.length : 0;
    }

    /**
     * Hook for subclassers to return tooltip texts for certain points on the
     * handle.
     *
     * @param e the e
     * @return the tool tip text
     */
    public String getToolTipText(MouseEvent e) {
        return null;
    }

    /**
     * Returns the index of the handle at the given location.
     *
     * @param x X-coordinate of the location.
     * @param y Y-coordinate of the location.
     * @return Returns the handle index for the given location.
     */
    public int getIndexAt(int x, int y) {
        if (handles != null && isHandlesVisible()) {
            int tol = graphComponent.getTolerance();
            Rectangle rect = new Rectangle(x - tol / 2, y - tol / 2, tol, tol);

            for (int i = handles.length - 1; i >= 0; i--) {
                if (isHandleVisible(i) && handles[i].intersects(rect)) {
                    return i;
                }
            }
        }

        return -1;
    }

    /**
     * Processes the given event.
     *
     * @param e the e
     */
    public void mousePressed(MouseEvent e) {
        if (!e.isConsumed()) {
            int tmp = getIndexAt(e.getX(), e.getY());

            if (!isIgnoredEvent(e) && tmp >= 0 && isHandleEnabled(tmp)) {
                graphComponent.stopEditing(true);
                start(e, tmp);
                e.consume();
            }
        }
    }

    /**
     * Processes the given event.
     *
     * @param e the e
     */
    public void mouseMoved(MouseEvent e) {
        if (!e.isConsumed() && handles != null) {
            int index = getIndexAt(e.getX(), e.getY());

            if (index >= 0 && isHandleEnabled(index)) {
                Cursor cursor = getCursor(e, index);

                if (cursor != null) {
                    graphComponent.getGraphControl().setCursor(cursor);
                    e.consume();
                } else {
                    graphComponent.getGraphControl().setCursor(
                            new Cursor(Cursor.HAND_CURSOR));
                }
            }
        }
    }

    /**
     * Processes the given event.
     *
     * @param e the e
     */
    public void mouseDragged(MouseEvent e) {
        // empty
    }

    /**
     * Processes the given event.
     *
     * @param e the e
     */
    public void mouseReleased(MouseEvent e) {
        reset();
    }

    /**
     * Starts handling a gesture at the given handle index.
     *
     * @param e     the e
     * @param index the index
     */
    public void start(MouseEvent e, int index) {
        this.index = index;
        first = e.getPoint();
        preview = createPreview();

        if (preview != null) {
            graphComponent.getGraphControl().add(preview, 0);
        }
    }

    /**
     * Returns true if the given event should be ignored.
     *
     * @param e the e
     * @return the boolean
     */
    protected boolean isIgnoredEvent(MouseEvent e) {
        return graphComponent.isEditEvent(e);
    }

    /**
     * Creates the preview for this handler.
     *
     * @return the j component
     */
    protected JComponent createPreview() {
        return null;
    }

    /**
     * Resets the state of the handler and removes the preview.
     */
    public void reset() {
        if (preview != null) {
            preview.setVisible(false);
            preview.getParent().remove(preview);
            preview = null;
        }

        first = null;
    }

    /**
     * Returns the cursor for the given event and handle.
     *
     * @param e     the e
     * @param index the index
     * @return the cursor
     */
    protected Cursor getCursor(MouseEvent e, int index) {
        return null;
    }

    /**
     * Paints the visible handles of this handler.
     *
     * @param g the g
     */
    public void paint(Graphics g) {
        if (handles != null && isHandlesVisible()) {
            for (int i = 0; i < handles.length; i++) {
                if (isHandleVisible(i)
                        && g.hitClip(handles[i].x, handles[i].y,
                        handles[i].width, handles[i].height)) {
                    g.setColor(getHandleFillColor(i));
                    g.fillRect(handles[i].x, handles[i].y, handles[i].width,
                            handles[i].height);

                    g.setColor(getHandleBorderColor(i));
                    g.drawRect(handles[i].x, handles[i].y,
                            handles[i].width - 1, handles[i].height - 1);
                }
            }
        }
    }

    /**
     * Returns the color used to draw the selection border. This implementation
     * returns null.
     *
     * @return the selection color
     */
    public Color getSelectionColor() {
        return null;
    }

    /**
     * Returns the stroke used to draw the selection border. This implementation
     * returns null.
     *
     * @return the selection stroke
     */
    public Stroke getSelectionStroke() {
        return null;
    }

    /**
     * Returns true if the handle at the specified index is enabled.
     *
     * @param index the index
     * @return the boolean
     */
    protected boolean isHandleEnabled(int index) {
        return true;
    }

    /**
     * Returns true if the handle at the specified index is visible.
     *
     * @param index the index
     * @return the boolean
     */
    protected boolean isHandleVisible(int index) {
        return !isLabel(index) || isLabelMovable();
    }

    /**
     * Returns the color to be used to fill the handle at the specified index.
     *
     * @param index the index
     * @return the handle fill color
     */
    protected Color getHandleFillColor(int index) {
        if (isLabel(index)) {
            return mxSwingConstants.LABEL_HANDLE_FILLCOLOR;
        }

        return mxSwingConstants.HANDLE_FILLCOLOR;
    }

    /**
     * Returns the border color of the handle at the specified index.
     *
     * @param index the index
     * @return the handle border color
     */
    protected Color getHandleBorderColor(int index) {
        return mxSwingConstants.HANDLE_BORDERCOLOR;
    }

    /**
     * Invoked when the handler is no longer used. This is an empty
     * hook for subclassers.
     */
    protected void destroy() {
        // nop
    }

}
